﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;


#if !NET8_0_OR_GREATER
using DimensionsHelper.Common.Utils;
#endif

namespace DimensionsHelper.Common.Metadata.Axis;

/// <summary>
///     轴表达式中的元素类型
/// </summary>
public enum AxisElementType
{
    /// <summary>
    ///     单个分类值
    /// </summary>
    Category,

    /// <summary>
    ///     分类区间
    /// </summary>
    CategoryRange,

    /// <summary>
    ///     文本，text()
    /// </summary>
    Text,

    /// <summary>
    ///     基数， base(['Expression Text'])
    /// </summary>
    Base,

    /// <summary>
    ///     未加权的基数，unweightedbase(['Expression Text'])
    /// </summary>
    UnweightedBase,

    /// <summary>
    ///     有效基数，effectivebase()
    /// </summary>
    EffectiveBase,

    /// <summary>
    ///     表达式， expression()
    /// </summary>
    Expression,

    /// <summary>
    ///     数值，numeric()
    /// </summary>
    Numeric,

    /// <summary>
    ///     派生值，derived( 'Expression Text' )
    /// </summary>
    Derived,

    /// <summary>
    ///     均值，mean([NumericVariable], ['Expression Text'])
    /// </summary>
    Mean,

    /// <summary>
    ///     标准误差，stderr([ NumericVariable ], ['Expression Text'])
    /// </summary>
    StdErr,

    /// <summary>
    ///     标准偏差，stddev([NumericVariable], ['Expression Text'])
    /// </summary>
    StdDev,

    /// <summary>
    ///     样本方差，sampvar([NumericVariable], ['Expression Text'])
    /// </summary>
    SampVar,

    /// <summary>
    ///     总计，total()
    /// </summary>
    Total,

    /// <summary>
    ///     小计，subtotal()
    /// </summary>
    SubTotal,

    /// <summary>
    ///     最小值，min()
    /// </summary>
    Min,

    /// <summary>
    ///     最大值，max()
    /// </summary>
    Max,

    /// <summary>
    ///     合并并出示，net({...})
    /// </summary>
    Net,

    /// <summary>
    ///     合并不出示，combine({..})
    /// </summary>
    Combine,

    /// <summary>
    ///     求和，sum()
    /// </summary>
    Sum,

    /// <summary>
    ///     中位数，median()
    /// </summary>
    Median,

    /// <summary>
    ///     百分位数，percentile()
    /// </summary>
    Percentile,

    /// <summary>
    ///     众数，mode()
    /// </summary>
    Mode,

    /// <summary>
    ///     ntd()
    /// </summary>
    Ntd,

    /// <summary>
    ///     Paired Preference, ppt()
    /// </summary>
    Ppt
}


/// <summary>
///     轴元素参数值类型
/// </summary>
public enum AxisElementParameterType
{
    /// <summary>
    ///     字符串类型，使用单引号包裹 'String'
    /// </summary>
    String,

    /// <summary>
    ///     标识符名称
    /// </summary>
    Name,

    /// <summary>
    ///     数字
    /// </summary>
    Number,

    /// <summary>
    ///     布尔值
    /// </summary>
    Boolean,

    /// <summary>
    ///     分类元素
    /// </summary>
    Element
}


/// <summary>
///     轴元素使用的参数，例如，base('True') 中的 'True'
/// </summary>
public class AxisElementParameter(AxisElementParameterType type, object value)
{
    public AxisElementParameterType Type { get; } = type;

    public object Value { get; } = value;

    public override string ToString()
    {
        if (Type == AxisElementParameterType.String)
        {
            return $"'{Value.ToString() ?? ""}'";
        }

        return Value.ToString() ?? "";
    }
}


/// <summary>
///     轴元素
/// </summary>
public class AxisElement : IAxisExclusive
{
    private static readonly Type[] SuffixDataTypes =
    [
        typeof(AxisElementCalculationScope),
        typeof(bool),
        typeof(int),
        typeof(double),
        typeof(bool),
        typeof(bool),
        typeof(bool),
        typeof(bool),
        typeof(bool),
        typeof(bool),
        typeof(string),
        typeof(string)
    ];

    private readonly LinkedList<AxisElement> _elements;

    private readonly HashSet<string> _excludeNames;
    private readonly Dictionary<string, LinkedListNode<AxisElement>> _nameToNode;
    private readonly List<AxisElementParameter> _parameters;
    private readonly AxisElementSuffix?[] _suffixes;

    internal AxisElement(AxisElementType type)
    {
        Type = type;
        _suffixes = new AxisElementSuffix[(int)AxisElementSuffixType.Weight + 1];
        _parameters = [];
        _elements = [];
        _excludeNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        _nameToNode = new Dictionary<string, LinkedListNode<AxisElement>>(StringComparer.OrdinalIgnoreCase);
    }


    /// <summary>
    ///     轴元素类型
    /// </summary>
    public AxisElementType Type { get; }


    /// <summary>
    ///     分类元素名，可以是null。当<see cref="Type" />是<see cref="AxisElementType.Category" />
    ///     时，配置此属性是分类元素名
    /// </summary>
    public string? Name { get; set; }


    /// <summary>
    ///     区间上限
    /// </summary>
    public string UpperBound { get; set; } = string.Empty;


    /// <summary>
    ///     区间下限
    /// </summary>
    public string LowerBound { get; set; } = string.Empty;


    /// <summary>
    ///     分类元素的描述，可以是null
    /// </summary>
    public string? Description { get; set; }


    /// <summary>
    ///     是否是带有'^'的分类元素或分类区间，只有<see cref="Type" />是<see cref="AxisElementType.Category" />或
    ///     <see cref="AxisElementType.CategoryRange" />时才有效。
    /// </summary>
    public bool IsExclude { get;
#if NET8_0_OR_GREATER
        init;
#else
            set;
#endif
    }


    /// <summary>
    ///     获取当前元素的子元素。
    /// </summary>
    public IEnumerable<AxisElement> Elements => _elements;


    /// <summary>
    ///     获取当前元素的类型参数。
    /// </summary>
    public IReadOnlyList<AxisElementParameter> Parameters => _parameters;


    /// <summary>
    ///     排除指定的分类名称，只有在<see cref="Type" />是<see cref="AxisElementType.Net" />或
    ///     <see cref="AxisElementType.Combine" />时才有用。
    /// </summary>
    /// <param name="names"></param>
    public void ExcludeNames(params string[] names)
    {
        if (Type != AxisElementType.Net && Type != AxisElementType.Combine)
        {
            return;
        }

        foreach (string name in names)
        {
            if (_nameToNode.Remove(name, out var node))
            {
                _elements.Remove(node);
            }

            _excludeNames.Add(name);
        }
    }

    // 只有 Type = AxisElementType.Net 或 AxisElementType.Combine 时才能接收轴元素。
    public void Append(AxisElement element)
    {
        if ((Type != AxisElementType.Net && Type != AxisElementType.Combine) ||
            string.IsNullOrEmpty(element.Name))
        {
            return;
        }

        if (Type == AxisElementType.Combine &&
            element.Type != AxisElementType.Category &&
            element.Type != AxisElementType.CategoryRange &&
            element.Type != AxisElementType.Combine &&
            element.Type != AxisElementType.Net)
        {
            throw new Exception($"在'combine'中添加'{element.Type}'类型的元素是无意义的。");
        }

        if (element.IsExclude)
        {
            if (element.Type == AxisElementType.Category)
            {
                ExcludeNames(element.Name!);
            }
            else
            {
                return;
            }
        }
        else if (!_excludeNames.Contains(element.Name!))
        {
            _nameToNode.Add(element.Name!, _elements.AddLast(element));
            ElementAdded?.Invoke(element);
            element.ElementAdded += OnElementAdded;
        }

        _parameters.Add(new AxisElementParameter(AxisElementParameterType.Element, element));
    }


    public void Remove(AxisElement element)
    {
        if (string.IsNullOrEmpty(element.Name))
        {
            if (_elements.Remove(element))
            {
                element.ElementAdded -= OnElementAdded;
            }

            return;
        }

        if (_nameToNode.Remove(element.Name!, out var node))
        {
            _elements.Remove(node);
            element.ElementAdded -= OnElementAdded;
        }
    }


    public IEnumerator<AxisElement> GetEnumerator()
    {
        return _elements.GetEnumerator();
    }


    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }


    internal event Action<AxisElement>? ElementAdded;

    private void OnElementAdded(AxisElement element)
    {
        ElementAdded?.Invoke(element);
    }


    /// <summary>
    ///     设置后缀值
    /// </summary>
    /// <param name="type">后缀类型</param>
    /// <param name="value">后缀值</param>
    public void SetSuffix(AxisElementSuffixType type, object? value)
    {
        if (value == null)
        {
            _suffixes[(int)type] = null;
            return;
        }

        Type suffixType = SuffixDataTypes[(int)type];

        if (!value.GetType().IsAssignableTo(suffixType))
        {
            throw new Exception($"数据类型不匹配：后缀'{type}'需要'{suffixType.Name}'类型的数据。");
        }

        var suffix = _suffixes[(int)type];

        if (suffix == null)
        {
            _suffixes[(int)type] = new AxisElementSuffix(type) { Value = value };
        }
        else
        {
            suffix.Value = value;
        }
    }


    /// <summary>
    ///     如果轴元素需要参数，使用此方法按顺序添加参数。
    ///     如果添加的类型是<see cref="AxisElement" />，添加的对象会同时被添加到
    ///     <see cref="Elements" />集合中。
    /// </summary>
    /// <param name="type">轴参数类型</param>
    /// <param name="parameter">参数值</param>
    public void AddParameter(AxisElementParameterType type, object parameter)
    {
        if (parameter is AxisElement e)
        {
            Debug.Assert(type == AxisElementParameterType.Element);
            Append(e);
        }
        else
        {
            _parameters.Add(new AxisElementParameter(type, parameter));
        }
    }


    /// <summary>
    ///     赋值给定轴元素的后缀配置到当前对象。
    /// </summary>
    public void CopySuffixes(AxisElement source)
    {
        for (int i = 0; i < source._suffixes.Length; i++)
        {
            _suffixes[i] = source._suffixes[i]?.Clone() as AxisElementSuffix;
        }
    }


    private void CopyParameters(AxisElement source)
    {
        _parameters.Clear();

        foreach (AxisElementParameter p in source.Parameters)
        {
            switch (p.Type)
            {
                case AxisElementParameterType.String:
                case AxisElementParameterType.Name:
                case AxisElementParameterType.Number:
                case AxisElementParameterType.Boolean:
                    AddParameter(p.Type, p.Value);
                    break;

                case AxisElementParameterType.Element:
                    AddParameter(AxisElementParameterType.Element, ((AxisElement)p.Value).Copy());
                    break;
            }
        }
    }


    private AxisElement Copy()
    {
        var element = new AxisElement(Type)
        {
            Name = Name,
            Description = Description,
            IsExclude = IsExclude,
            LowerBound = LowerBound,
            UpperBound = UpperBound
        };

        element.CopyParameters(this);
        element.CopySuffixes(this);
        return element;
    }


    public override string ToString()
    {
        // 分类区间不需要考虑后缀和描述
        if (Type == AxisElementType.CategoryRange)
        {
            return $"{(IsExclude ? "^" : "")}{LowerBound}..{UpperBound}";
        }

        string suf = string.Join(", ", 
            _suffixes.Select(e => e?.ToString() ?? string.Empty)
                .Where(e => !string.IsNullOrEmpty(e)));

        if (suf.Length > 0)
        {
            suf = '[' + suf + ']';
        }

        var res = new StringBuilder();
        res.Append(Name);

        if (Description != null)
        {
            if (res.Length > 0)
            {
                res.Append(' ');
            }

            res.Append('\'')
                .Append(Description)
                .Append('\'');
        }

        // Categorical时，添加上后缀就可以完成了
        if (Type == AxisElementType.Category)
        {
            if (!string.IsNullOrEmpty(suf))
            {
                res.Append(' ').Append(suf);
            }

            return res.ToString();
        }

        if (res.Length > 0)
        {
            res.Append(' ');
        }

        res.Append(Type.ToString().ToLower())
            .Append('(');

        if (Type == AxisElementType.Net || Type == AxisElementType.Combine)
        {
            res.Append('{');
        }

        if (_parameters.Count > 0)
        {
            res.AppendJoin(", ", _parameters.Select(p => p.ToString()));
        }

        if (Type == AxisElementType.Net || Type == AxisElementType.Combine)
        {
            res.Append('}');
        }

        res.Append(')');

        if (!string.IsNullOrEmpty(suf))
        {
            res.Append(' ').Append(suf);
        }

        return res.ToString();
    }


    /// <summary>
    ///     获取字符串对应的元素类型。
    ///     <see cref="AxisElementType.Category" /> 和 <see cref="AxisElementType.CategoryRange" />
    ///     不包含在内。
    /// </summary>
    public static bool TryGetElementType(string value, out AxisElementType type)
    {
        type = AxisElementType.Category;

        if (string.IsNullOrEmpty(value))
        {
            return false;
        }

        if (!Enum.TryParse(value, true, out AxisElementType res))
        {
            return false;
        }

        type = res;
        return type != AxisElementType.Category && type != AxisElementType.CategoryRange;
    }
}